1 /** 2 gn_tracking uses an object literal notation: configuration objects sit snugly inside it, 3 alongside the various functions it exposes. 4 5 In reality, init() is the only function you should need to access directly 6 7 @namespace gn_tracking 8 */ 9 var gn_tracking = { 10 11 /** Internal config object; holds a Google object or string, a quantcast CSV string, and a ComScore object */ 12 config: { 13 google: { 14 urchin: '', 15 ga: '' 16 }, 17 google: '', 18 quantcast: '', 19 comscore: '' 20 }, 21 22 /** Internal flag store;, used mostly to kill successive calls to init */ 23 flags: { loaded: false }, 24 25 /** Accepts a config param, and then for each item in the config object, calls its matching function 26 @param {Object} config The config object; see the internal ga_tracking.config object for a reference to its structure 27 */ 28 init: function(config) { 29 var that = this; 30 31 // If our init flag has been triggered, quit now 32 if (typeof this.flags.initialised != 'undefined' && this.flags.initialised) { 33 return false; 34 // Else, set the flag and continue 35 } else { 36 this.flags.initialised = true; 37 } 38 39 // Overwrite our config object with the passed version 40 if (config) this.config = config; 41 42 // For each element in the config object, inspect gn_tracking for a function with a matching name and trigger it 43 for (var prop in this.config) { 44 if (!this.is_empty(this.config[prop]) && typeof this[prop] == 'function') { 45 this[prop](); 46 } 47 } 48 }, 49 50 /** Loads the required GA script, and queues a callback to be fired when the script loads */ 51 google: function() { 52 var that = this; 53 54 // Init our google tracking flags 55 this.flags.google = {}; 56 57 // If we have a CSV of GA tags... 58 if (typeof(this.config.google) == 'string') { 59 var ids = this.config.google.split(','); 60 61 // Split it, and populate the google config object 62 this.config.google = {}; 63 if (ids[0]) this.config.google.ga = ids[0]; 64 if (ids[1]) this.config.google.urchin = ids[1]; 65 } 66 67 // If we have a GA 2.0 api request, call the script, and queue the callback 68 if (this.config.google.ga) { 69 this.add_script( 70 'http://www.google-analytics.com/ga.js', 71 function() { 72 73 // The presense of pageTracker means that this page is already using the GA 2.0 APi; abort 74 if (typeof pageTracker != 'undefined' && !that.is_empty(pageTracker)) return false; 75 76 // GA payload 77 pageTracker = _gat._getTracker(that.config.google.ga); 78 pageTracker._initData(); 79 pageTracker._trackPageview(); 80 81 // Update gn_tracking with our success flag 82 that.flags.google.ga = true; 83 } 84 ); 85 } 86 87 // If we have a GA 1.0 api request, call the script, and queue the callback 88 if (this.config.google.urchin) { 89 this.add_script( 90 'http://www.google-analytics.com/urchin.js', 91 function() { 92 93 // The presense of _uacct means that this page is already using the GA 1.0 APi; abort 94 if (typeof _uacct != 'undefined' && !that.is_empty(_uacct)) return false; 95 96 // GA payload 97 _uacct = that.config.google.urchin; 98 urchinTracker(); 99 100 // Update gn_tracking with our success flag 101 that.flags.google.urchin = true; 102 } 103 ); 104 } 105 }, 106 107 108 /** Loads the required quantcast script, and queues a callback to be fired when the script loads */ 109 quantcast: function() { 110 var that = this; 111 112 this.add_script( 113 "http://edge.quantserve.com/quant.js", 114 function() { 115 116 // The presense of _qacct or _qoptions means that this page is already using quantcast tags; abort 117 if (typeof _qacct != 'undefined' && !that.is_empty(_qacct)) return false; 118 if (typeof _qoptions != 'undefined' && !that.is_empty(_qoptions)) return false; 119 120 // Split our CSV config string and call Quantserve() with each value 121 var ids = that.config.quantcast.split(','); 122 for (var i = 0; i < ids.length; i++) { 123 _qoptions={qacct: ids[i]}; 124 quantserve(); 125 } 126 127 // Update gn_tracking with our success flag 128 that.flags.quantcast = true; 129 } 130 ); 131 }, 132 133 134 /** Loads the required ComScore script, and queues a callback to be fired when the script loads */ 135 comscore: function() { 136 var that = this; 137 138 this.add_script( 139 (document.location.protocol == 'https:' ? 'https://sb' : 'http://b')+".scorecardresearch.com/beacon.js", 140 function() { 141 // Standard Comscore payload 142 COMSCORE.beacon(that.config.comscore); 143 144 // Update gn_tracking with our success flag 145 that.flags.comscore = true; 146 } 147 ); 148 }, 149 150 151 /** Aaah, the big deal here, add_script asyncronously adds a script object to the head, monitors its load status, 152 and then queues a callback to be executed when it's complete 153 @param {String} url Script to be attached to the page 154 @param {Function} callback Function to be executed when the script is fully loaded 155 */ 156 add_script: function(url, callback) { 157 var that = this; 158 159 160 var script = document.createElement("script"); 161 var head = document.getElementsByTagName("head")[0]; 162 script.src = url; 163 164 script.onload = script.onreadystatechange = function(){ 165 if (!this.readyState || this.readyState == "loaded" || this.readyState == "complete") { 166 if (typeof callback == 'function') { 167 callback(); 168 } 169 170 // Handle memory leak in IE 171 script.onload = script.onreadystatechange = null; 172 head.removeChild(script); 173 } 174 }; 175 176 head.appendChild(script); 177 }, 178 179 180 /** Accepts a variable and attempts to determine if it's truly 'empty'. Mostly used as Object's length property lies 181 @param value Any primitive or basic datatype (string, number, boolan, array, object, etc.) 182 */ 183 is_empty: function(value) { 184 switch (typeof(value)) { 185 case 'object': 186 // If this object has any properties directly defined on it... well, it's not empty then, is it? 187 for(var prop in value) { 188 if (value.hasOwnProperty(prop)) return false; 189 } 190 191 return true; 192 break; 193 194 // Strings and arrays are easy, just count the values 195 case 'string': 196 case 'array': 197 if (value.length > 0) { 198 return false; 199 } else { 200 return true; 201 } 202 203 break; 204 205 // Most other datatypes, by their existence, can't be empty; return false 206 default: 207 return false; 208 break; 209 } 210 } 211 }; 212